/**
  ******************************************************************************
  * @author     Chris
  * @since      2024/6/28 15:40
  *
  * @file       pd_wwdg.c
  * @brief      Xxx hardware driver.
  *
  * @note       This file contains the hardware driver for the Xxx.
  *
  * @warning    None.
  ******************************************************************************
  * Change Logs:
  *   Date          Author       Notes
  *   2024/6/28     Chris        the first version
  *
  ******************************************************************************
  */


#include <stdlib.h>
#include <stm32f4xx_ll_bus.h>
#include <stm32f4xx_ll_wwdg.h>
#include <stm32f4xx_ll_rcc.h>
#include "pd_wwdg.h"


static uint32_t WindowWatchdog_pow(uint32_t Y) {
    uint32_t Result = 1;
    while (Y--) Result *= 2;
    return Result;
}

/**
 * When reducing the WWDG clock frequency by decreasing the prescaler coefficient of APB1,
 * it is important to note that the frequencies of other peripherals on APB1 will also be
 * lowered simultaneously.
 * PCLK1 max freq:
 *  STM32F407    42MHz
 *  STM32F103    36MHz
 *  STM32G431   170MHz
 *
 * @param timeout   max/min window timeout ms
 * @param prescaler WWDG pre-scaler
 * @return
 */
static double WindowWatchdog_getCounter(uint32_t timeout, uint32_t prescaler) {
    uint32_t pclk1 = 0;

    if      (LL_RCC_GetAPB1Prescaler() == LL_RCC_APB1_DIV_1)  pclk1 = 168000000 / 1; // [-]
    else if (LL_RCC_GetAPB1Prescaler() == LL_RCC_APB1_DIV_2)  pclk1 = 168000000 / 2; // [-]
    else if (LL_RCC_GetAPB1Prescaler() == LL_RCC_APB1_DIV_4)  pclk1 = 168000000 / 4; // [0.098, 49.932]
    else if (LL_RCC_GetAPB1Prescaler() == LL_RCC_APB1_DIV_8)  pclk1 = 168000000 / 8; // [0.195, 99.864]
    else if (LL_RCC_GetAPB1Prescaler() == LL_RCC_APB1_DIV_16) pclk1 = 168000000 / 16;// [0.410, 209.715]
    else {}

    return 1.0 * timeout / 1000 * pclk1 / 4096 / WindowWatchdog_pow(prescaler >> 7);
}

/**
 * watchdog timeout: t = 1/PCLK1 * 4096 * 2^WDGTB[1:0] * (T[5:0] +1)
 * windown time: t = 1/PCLK1 * 4096 * 2^WDGTB[1:0] * (T[5:0] - W[5:0])
 *
 * @param super
 */
static void WinWDG_init(WDG *super) {
    WinWDG *this = (WinWDG *) super;

    LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_WWDG);
    LL_WWDG_SetPrescaler(this->WWDGx, this->Prescaler);
    LL_WWDG_SetWindow(this->WWDGx, this->Window);
    LL_WWDG_SetCounter(this->WWDGx, this->Counter);
    LL_WWDG_Enable(this->WWDGx);
}

static void WinWDG_feedDogs(WDG *super) {
    WinWDG *this = (WinWDG *) super;
    LL_WWDG_SetCounter(this->WWDGx, this->Counter);
}

static uint32_t WinWDG_isReset(WDG *super) {
    return LL_RCC_IsActiveFlag_WWDGRST() == SET ? 1 : 0;
}

static WinWDG *build(WWDG_TypeDef *WWDGx, uint32_t Prescaler, uint32_t maxTimeout, uint32_t minTimeout) {
    WinWDG *wwdg = malloc(sizeof(WinWDG));

    Driver_add(wwdg, GEN_PERM);

    wwdg->min = minTimeout;
    wwdg->max = maxTimeout;

    double counter = WindowWatchdog_getCounter(maxTimeout, Prescaler) - 1;
    double window = counter - WindowWatchdog_getCounter(minTimeout, Prescaler);

    wwdg->WWDGx        = WWDGx;
    wwdg->Prescaler    = Prescaler;
    wwdg->Window       = 0x40 | (uint8_t) window;
    wwdg->Counter      = 0x40 | (uint8_t) counter;

    wwdg->wdg          = *WDGBuilder.build();
    wwdg->wdg.init     = &WinWDG_init;
    wwdg->wdg.feedDogs = &WinWDG_feedDogs;
    wwdg->wdg.isReset  = &WinWDG_isReset;

    return wwdg;
}

const struct WinWDGClass WinWDGBuilder = {.build = &build};
